package com.changgou.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.changgou.seckill.config.ConfirmMessageSender;
import com.changgou.seckill.config.RabbitMQConfig;
import com.changgou.seckill.dao.SeckillOrderMapper;
import com.changgou.seckill.pojo.SeckillGoods;
import com.changgou.seckill.pojo.SeckillOrder;
import com.changgou.seckill.service.SecKillOrderService;
import com.changgou.util.IdWorker;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.concurrent.TimeUnit;

@Service
public class SecKillOrderServiceImpl implements SecKillOrderService {

    @Autowired
    private RedisTemplate redisTemplate;

    public static final String SECKILL_GOODS_KEY="seckill_goods_"; //常量

    public static final String SECKILL_GOODS_STOCK_COUNT_KEY="seckill_goods_stock_count_"; //常量

    @Autowired
    private IdWorker idWorker;

    @Autowired
    private ConfirmMessageSender confirmMessageSender;

    @Autowired
    private SeckillOrderMapper seckillOrderMapper;


   @Override
    public boolean add(Long id, String time, String username) {

        /*防止用户恶意刷单*/
        String result = this.preventRepeatCommit(username, id);
        if ("fail".equals(result)){ //说明当前不能访问(一旦返回false,下面的方法都将不会执行)
            return false;
        }

        /*防止相同商品重复购买*/
        SeckillOrder order = seckillOrderMapper.getOrderInfoByUserNameAndGoodsId(username, id);
        if (order != null){ //说明当前商品买过
            return false;
        }


        /*获取redis中的商品信息与库存信息,并进行判断*/
        //获取商品redis中得商品信息
        SeckillGoods seckillGoods = (SeckillGoods) redisTemplate.boundHashOps(SECKILL_GOODS_KEY+time).get(id);
        //获取redis中的库存信息(此时返回的类型必须是String类型,因为String存进去的数据就是以String类型存在)
        String redisStock = (String) redisTemplate.opsForValue().get(SECKILL_GOODS_STOCK_COUNT_KEY+id);
        if (StringUtils.isEmpty(redisStock)){
            return false;
        }
        //将String类型的数据转换成int类型
        int stock = Integer.parseInt(redisStock);
        if (seckillGoods == null || stock<=0){
            return false;
        }

        //执行redis的预扣减库存,并获取到扣减之后的库存值(扣减库存单的前提:当前用户进行秒杀商品购买的时候,每一个永辉对于相同的秒杀商品只能买一次并且只允许买一个,所以对库存的扣减,每次只能进行减1)
        //decrement:对当前传入的Key的值进行减一的操作并且他会将减完后的结果进行直接的返回
        //通过decrement完成预扣减库存操作,对于该方法来说其是一个原子性的操作(即要么同时成功,要么同时失败)
        Long decrement = redisTemplate.opsForValue().decrement(SECKILL_GOODS_STOCK_COUNT_KEY + id);
        if (decrement<=0){
            //扣减完库存之后,当前商品已经没有库存了.
            //删除redis中的商品信息与库存信息
            redisTemplate.boundHashOps(SECKILL_GOODS_KEY+time).delete(id);
            redisTemplate.delete(SECKILL_GOODS_STOCK_COUNT_KEY + id);
        }

        //发送消息(保证消息生产者对于消息的不丢失实现)
        //消息体: 秒杀订单
        SeckillOrder seckillOrder = new SeckillOrder();
        seckillOrder.setId(idWorker.nextId()); //其的id值需要通过雪花算法来生成
        seckillOrder.setSeckillId(id);
        seckillOrder.setMoney(seckillGoods.getCostPrice()); //获取价钱
        seckillOrder.setUserId(username);
        seckillOrder.setSellerId(seckillGoods.getSellerId()); //SellerId指的是当前商家的id
        seckillOrder.setCreateTime(new Date());
        seckillOrder.setStatus("0"); //将状态设置成0

        //发送消息
        confirmMessageSender.sendMessage("", RabbitMQConfig.SECKILL_ORDER_QUEUE, JSON.toJSONString(seckillOrder));

        return true;
    }


    /**
     * 防止用户恶意刷单
     * @param username
     * @param id
     * @return
     */
    private String preventRepeatCommit(String username,Long id){
        String redis_key = "seckill_user_"+username+"_id_"+id;

        long count = redisTemplate.opsForValue().increment(redis_key, 1); //increment自动增长,会对当前key中的值不断进行加一的操作并且它能保存我们当前操作的原子性
        if (count == 1){
            //代表当前用户是第一次访问.
            //对当前的key设置一个五分钟的有效期
            redisTemplate.expire(redis_key,5, TimeUnit.MINUTES);
            return "success";
        }

        if (count>1){
            return "fail";
        }

        return "fail";
    }
}
